Skip to content

Conversation

@cacieprins
Copy link
Contributor

@cacieprins cacieprins commented Oct 22, 2025

  • Closes

Additional details

This is a DRAFT proposal

In order to verify performance improvements, this adds a "stress test" e2e test to the driver that simulates interacting with extremely large virtual scrolling lists. This test should probably not be run with "legacy" visibility, as it immediately causes the browser to eat up all available memory and crash. This fixture works perfectly fine outside of Cypress.

This introduces an experimentalFastVisibility option, that switches our visibility detections to an alternative algorithm. This algorithm has some caveats, but it is much faster than the current visibility algorithm.

Cypress uses this visibility algorithm not just for visibility assertions, but also for every interaction that requires "interactability." It's memory intensive and can cause unnecessary layout thrashing due to repeated access of CSS properties that require layout recaulcation.

The "fast" visibility algorithm:

  1. Assumes body and html are always visible; this is in line with current visibility behavior
  2. Uses the built-in checkVisibility method as first pass, with all options enabled. The following states are considered hidden by this method:
  • The element does not have an associated box
  • The element is not being rendered because it or one of its ancestors has set content-visibility to `hidden
  • It has an opacity of 0
  • The value of its visibility property makes it invisible
  • content-visibility CSS value is auto, and its derived value prevents the element from being rendered
  1. If the element is an <option> or <optgroup>, it defers to the visibility of the parent <select> element. If there is no parent <select> element (an invalid DOM tree state), the element is considered hidden.
  2. If the element is still considered visible, it performs a more comprehensive check with an adaptive point sampling algorithm

The point sampling algorithm:

  1. Implements a visibleAtPoint check, which uses document.elementFromPoint to determine which element is at the top of the render context at that point. If the element at that point is the subject element or a child of the subject element, the subject element is considered visible at that point.
  2. From the bounding rectangle of the subject element, it checks the four corners and center. If any of these points are visible, the subject element is considered visible.
  3. If none of these points are visible, it divides the bounding rectangle in to four sub-rectangles, and performs the same sampling on them.
  4. The sampling algorithm returns true if any part of the element is visible, but for performance constraints it will only subdivide to a limited depth, and will stop subdividing if the sampling rectangles fall below 1px in both dimensions.

Benefits over the legacy algorithm:

  • Constant-time in the best case, and bounded exponential in the worst case (point sampling on a fully hidden element)
  • Detects when an element is fully covered by positioned elements outside its ancestor tree
  • Can be tweaked to provide access to threshold-based visibility
  • Could be extended to require a minimum visible size for an element to be considered actionable tap events, to test conformation with UX guidelines regarding the size of touch targets
  • Does not crash when running the virtual scroll stress tests

Caveats:

  • Elements that are outside the bounds of the browser viewport will always be considered hidden with this algorithm
  • Elements with pointer-events:none either explicit or inherited will always be considered hidden
  • <option> elements that are not a direct child of <select> or <optgroup> elements are not considered visible
  • <optgroup> elements that are not a direct child of <select> or <optgroup> elements are not considered visible
  • Certain other edge cases that are considered hidden to this algorithm may be visible to the user, especially when it comes to elements that have a 0 height or width and have visible children. In these cases, it may be better to assert visibility on the visible children rather than the containing element.
  • Shadow dom support is unknown at this time
  • Some of the current visibility.cy.ts tests fail because the subject element is either off-screen, or covered by an absolute/fixed element that is not an ancestor.
  • If even 1px of the subject element is visible, it will be considered visible. Potential consideration: configurable threshold?

Future performance improvements:

  • Memoize elementFromPoint calls
  • delete old records from memoization maps to reduce memory footprint
  • Use requestAnimationFrame to measure the BoundingClientRect of the subject element; this is difficult due to how the visibility & interactability checks are currently wired through jquery selectors, which prevents this method from being async

Steps to test

How has the user experience changed?

PR Tasks

@cypress
Copy link

cypress bot commented Oct 23, 2025

cypress    Run #66993

Run Properties:  status check canceled Cancelled #66993  •  git commit 4f8b8fcb8b: Merge branch 'develop' into visibility-performance
Project cypress
Branch Review visibility-performance
Run status status check canceled Cancelled #66993
Run duration 40m 05s
Commit git commit 4f8b8fcb8b: Merge branch 'develop' into visibility-performance
Committer Cacie Prins
View all properties for this run ↗︎

Test results
Tests that failed  Failures 172
Tests that were flaky  Flaky 4
Tests that did not run due to a developer annotating a test with .skip  Pending 833
Tests that did not run due to a failure in a mocha hook  Skipped 37
Tests that passed  Passing 9057
View all changes introduced in this branch ↗︎

Warning

Partial Report: The results for the Application Quality reports may be incomplete.

UI Coverage  45.48%
  Untested elements 188  
  Tested elements 161  
Accessibility  97.98%
  Failed rules  4 critical   8 serious   2 moderate   2 minor
  Failed elements 101  

Tests for review

Failed  dom/visibility.cy.ts • 42 failed tests • 5x-driver-electron

View Output

Test Artifacts
... > basic CSS properties > detects visibility for visibility-property test cases Test Replay
... > basic CSS properties > detects visibility for display-property test cases Test Replay
... > basic CSS properties > detects visibility for opacity-property test cases Test Replay
... > basic CSS properties > detects visibility for table-elements test cases Test Replay
... > basic CSS properties > detects visibility for contain-property test cases Test Replay
... > basic CSS properties > detects visibility for pointer-events-none test cases Test Replay
... > form elements > detects visibility for select-and-option-elements test cases Test Replay
... > form elements > detects visibility for optgroup-elements test cases Test Replay
... > form elements > detects visibility for options-outside-select test cases Test Replay
... > form elements > detects visibility for input-elements test cases Test Replay
The first 10 failed tests are shown, see all 42 tests in Cypress Cloud.
Failed  commands/actions/click.cy.ts • 0 failed tests • 5x-driver-electron

View Output

Test Artifacts
Failed  commands/actions/type_special_chars.cy.ts • 0 failed tests • 5x-driver-electron

View Output

Test Artifacts
Failed  commands/querying/querying.cy.ts • 0 failed tests • 5x-driver-electron

View Output

Test Artifacts
Failed  e2e/origin/cookie_behavior.cy.ts • 0 failed tests • 5x-driver-electron

View Output

Test Artifacts

The first 5 failed specs are shown, see all 870 specs in Cypress Cloud.

Flakiness  cypress/e2e/e2e/origin/config_env.cy.ts • 1 flaky test • 5x-driver-inject-document-domain-chrome:beta

View Output

Test Artifacts
cy.origin- Cypress.config() > serializable > overwrites different values in secondary if one exists in the primary Test Replay
Flakiness  cypress/e2e/commands/net_stubbing.cy.ts • 1 flaky test • 5x-driver-firefox

View Output

Test Artifacts
... > stops waiting when an xhr request is canceled
    </td>
  </tr></table>
Flakiness  cypress/e2e/e2e/origin/config_env.cy.ts • 1 flaky test • 5x-driver-inject-document-domain-chrome

View Output

Test Artifacts
cy.origin- Cypress.config() > serializable > overwrites different values in secondary if one exists in the primary Test Replay
Flakiness  src/runner/selector-playground/SelectorPlayground.cy.tsx • 1 flaky test • app-ct

View Output

Test Artifacts
SelectorPlayground > copies selector text Test Replay

@cacieprins cacieprins force-pushed the visibility-performance branch from 5c38a87 to f550346 Compare October 28, 2025 14:30
@cacieprins cacieprins changed the title feat: experimental "fast mode" for visibility checks perf: experimental "fast mode" for visibility checks Oct 30, 2025
Comment on lines +1 to +2
# Fast Visibility Algorithm Migration Guide

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cacieprins Been meaning to ask how much you see portions of this being in the https://docs.cypress.io/app/references/experiments page vs here. I was thinking there could be some basic explanation in the docs and then link to here since it's quite extensive.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants